package org.weso.lodin.analyzer;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexDeletionPolicy;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.RAMDirectory;
import org.weso.lodin.appserv.TransformerAppServ;
import org.weso.lodin.exceptions.LodinModelException;
import org.weso.lodin.generated.CountryEnum;
import org.weso.lodin.to.CountryTO;
import org.weso.lodin.to.ObservationTO;
import org.weso.lodin.to.RankingScoreObservationTO;
import org.weso.lodin.to.ScoreTO;


public class CountryReconciliator {

	private static final String COUNTRY_NAME_FIELD = "name";
	private static final String COUNTRY_CODE_FIELD = "code";
	private static final int MAX_RESULTS = 1;

	private RAMDirectory idx;
	private IndexSearcher indexSearcher;
	Analyzer countryAnalyzer = new CountryAnalyzer();
	private List<CountryTO> countries;

	protected static Logger logger = Logger.getLogger(CountryReconciliator.class);

	public CountryReconciliator(List<CountryTO> countries){
		try {
			this.countries = countries;
			this.idx = createRamDirectory(countries);
			this.indexSearcher = new IndexSearcher(idx);
		} catch (Exception e) {
			throw new LodinModelException(e, "Creating Lucene Index");
		}
	}

	private RAMDirectory createRamDirectory(List<CountryTO> countries) throws CorruptIndexException, LockObtainFailedException, IOException {
		RAMDirectory idx = new RAMDirectory();

		boolean create = true;
		IndexDeletionPolicy deletionPolicy = 
				new KeepOnlyLastCommitDeletionPolicy(); 
		IndexWriter indexWriter = 
				new IndexWriter(idx,countryAnalyzer,create,
						deletionPolicy,IndexWriter.MaxFieldLength.UNLIMITED);
		for(CountryTO country: countries){
			Field code =
					new Field(COUNTRY_CODE_FIELD,country.getCode(),Field.Store.YES,Field.Index.NOT_ANALYZED);
			Field subjectField = 
					new Field(COUNTRY_NAME_FIELD,country.getName(),Field.Store.YES,Field.Index.ANALYZED);
			Document doc = new Document();
			doc.add(code);
			doc.add(subjectField);
			indexWriter.addDocument(doc);
		}
		indexWriter.optimize();
		indexWriter.close();
		return idx;
	}

	public List<CountryTO> search (String query){
		List<CountryTO> results = new LinkedList<CountryTO>();
		query = clean(query);
		try {
			Query createQueryFromString = createQueryFromString(query);
			ScoreDoc[] scoreDocs = fetchSearchResults(
					createQueryFromString, indexSearcher, MAX_RESULTS);	
			for(ScoreDoc scoredoc: scoreDocs){
				Document doc = indexSearcher.doc(scoredoc.doc);
				CountryTO country = new CountryTO();
				country.setCode(doc.getField(COUNTRY_CODE_FIELD).stringValue());
				country.setName(doc.getField(COUNTRY_NAME_FIELD).stringValue());
				results.add(country);
			}
		}catch(Exception e){
			throw new LodinModelException(e, "Searching country "+query+" invalid");
		}	
		return results;
	}

	//FIXME: extract to filter
	private String clean(String query) {
		return query.replaceAll("ô", "o").replaceAll("\\*", " ").replaceAll("'", "");
	}

	public List<CountryTO> searchByCodeISO3 (String code){
		List<CountryTO> results = new LinkedList<CountryTO>();
		int i = 0;
		boolean found = Boolean.FALSE;
		while(i<countries.size() && !found){
			CountryTO country = countries.get(i);
			if(country.getCode().equalsIgnoreCase(code)){
				results.add(country);
				found = Boolean.TRUE;
			}
			i++;
		}
		return results;
	}


	public List<CountryTO> searchByCodeISO2 (String code){
		List<CountryTO> results = new LinkedList<CountryTO>();
		int i = 0;
		boolean found = Boolean.FALSE;
		while(i<countries.size() && !found){
			CountryTO country = countries.get(i);
			if(country.getIso2().equalsIgnoreCase(code)){
				results.add(country);
				found = Boolean.TRUE;
			}
			i++;
		}
		return results;
	}

	public Query createQueryFromString(String q) throws ParseException {
		QueryParser parser = new QueryParser(COUNTRY_NAME_FIELD,this.countryAnalyzer);
		parser.setDefaultOperator(QueryParser.Operator.OR);		
		return parser.parse(q);
	}

	private static ScoreDoc[] fetchSearchResults(Query query, Searcher indexSearcher, int n ){
		try{
			TopScoreDocCollector collector = TopScoreDocCollector.create(n, true);
			indexSearcher.search(query, collector);
			ScoreDoc[] scoreDocs = collector.topDocs().scoreDocs;
			return scoreDocs;
		}catch(IOException e){
			throw new LodinModelException(e, "Fetching results "+query);
		}
	}

	public List<CountryTO> search(ObservationTO observation) {
		logger.debug("Looking up country "+observation.getCountry()+" by "+observation.getCountryType().toString());
		List<CountryTO> results = new LinkedList<CountryTO>();
		if(observation.getCountryType().equals(CountryEnum.NAME)){			
			results = this.search(observation.getCountry());
		}else if(observation.getCountryType().equals(CountryEnum.ISO_CODE_3)){
			results = this.searchByCodeISO3(observation.getCountry());
		}else {
			results = this.searchByCodeISO2(observation.getCountry());
		}		
		if(results.size()==0){
			return this.searchAlt(observation);
		}else return results;
	}
	
	public List<CountryTO> searchToWB(ObservationTO observation) {
		logger.debug("Looking up country "+observation.getCountry()+" by "+observation.getCountryType().toString());
		return this.searchByCodeISO2(observation.getCountry());		
	}
	
	public List<CountryTO> search(ScoreTO score) {
		logger.debug("Looking up country "+score.getCountry());
		return  this.search(score.getCountry());
		
	}

	public List<CountryTO> searchAlt(ObservationTO observation) {
		if(observation.getCountryTypeAlt()!= null && 
				observation.getCountryAlt()!=null && 
				!observation.getCountryAlt().equalsIgnoreCase("")){
			//logger.info("ALT Looking up country "+observation.getCountry()+" by "+observation.getCountryTypeAlt()+" value "+observation.getCountryAlt());			
			if(observation.getCountryTypeAlt().equals(CountryEnum.NAME)){			
				return this.search(observation.getCountryAlt());
			}else if(observation.getCountryTypeAlt().equals(CountryEnum.ISO_CODE_3)){
				return this.searchByCodeISO3(observation.getCountryAlt());
			}else {
				return this.searchByCodeISO2(observation.getCountryAlt());
			}
		}else return new LinkedList<CountryTO>();

	}

	public List<CountryTO> searchAlt(ScoreTO score) {

		return this.search(score.getCountry());

	}

	

	//FIXME: Refactor RankingScoreObservationTO should be aggreate an ObservationTO
		public List<CountryTO> search(RankingScoreObservationTO observation) {
		logger.debug("Looking up country "+observation.getCountry()+" by "+observation.getCountryType().toString());
		List<CountryTO> results = new LinkedList<CountryTO>();
		if(observation.getCountryType().equals(CountryEnum.NAME)){			
			results = this.search(observation.getCountry());
		}else if(observation.getCountryType().equals(CountryEnum.ISO_CODE_3)){
			results = this.searchByCodeISO3(observation.getCountry());
		}else {
			results = this.searchByCodeISO2(observation.getCountry());
		}		
		if(results.size()==0){
			return this.searchAlt(observation);
		}else return results;
	}

	public List<CountryTO> searchAlt(RankingScoreObservationTO observation) {
		if(observation.getCountryTypeAlt()!= null && 
				observation.getCountryAlt()!=null && 
				!observation.getCountryAlt().equalsIgnoreCase("")){
			if(observation.getCountryTypeAlt().equals(CountryEnum.NAME)){			
				return this.search(observation.getCountryAlt());
			}else if(observation.getCountryTypeAlt().equals(CountryEnum.ISO_CODE_3)){
			  return this.searchByCodeISO3(observation.getCountryAlt());
			}else {
				return this.searchByCodeISO2(observation.getCountryAlt());
			}
		}else return new LinkedList<CountryTO>();
		
	}

}
